/**
 * Created by elect on 21/02/17.
 */

package glNext.tut01


import com.jogamp.newt.event.KeyEvent
import com.jogamp.opengl.GL2ES2.*
import com.jogamp.opengl.GL3
import com.jogamp.opengl.GL3ES3.GL_GEOMETRY_SHADER
import glNext.*
import main.framework.Framework
import uno.buffer.destroyBuffers
import uno.buffer.floatBufferOf
import uno.buffer.intBufferBig


fun main(args: Array<String>) {
    HelloTriangle_Next().setup("Tutorial 01 - Hello Triangle")
}

class HelloTriangle_Next : Framework() {

    val strVertexShader = """
            #version 330

            #define POSITION 0

            layout (location = POSITION) in vec4 position;

            void main()
            {
                gl_Position = position;
            }
    """

    val strFragmentShader = """
        #version 330

        #define FRAG_COLOR  0

        layout (location = FRAG_COLOR) out vec4 outputColor;

        void main()
        {
            outputColor = vec4(1.0f, 1.0f, 1.0f, 1.0f);
        }
    """

    val vertexPositions = floatBufferOf(
            +0.75f, +0.75f, 0.0f, 1.0f,
            +0.75f, -0.75f, 0.0f, 1.0f,
            -0.75f, -0.75f, 0.0f, 1.0f)

    var theProgram = 0
    val positionBufferObject = intBufferBig(1)
    val vao = intBufferBig(1)


    /**
     * Called after the window and OpenGL are initialized. Called exactly once, before the main loop.
     * @param gl
     */
    override fun init(gl: GL3) = with(gl) {

        initializeProgram(gl)

        initializeVertexBuffer(gl)

        glGenVertexArray(vao)
        glBindVertexArray(vao)
    }

    fun initializeProgram(gl: GL3) {

        val shaderList = listOf(createShader(gl, GL_VERTEX_SHADER, strVertexShader),
                createShader(gl, GL_FRAGMENT_SHADER, strFragmentShader))

        theProgram = createProgram(gl, shaderList)

        shaderList.forEach(gl::glDeleteShader)
    }

    fun createShader(gl: GL3, shaderType: Int, shaderFile: String): Int = with(gl) {

        val shader = glCreateShader(shaderType)
        glShaderSource(shader, shaderFile)

        glCompileShader(shader)

        val status = glGetShader(shader, GL_COMPILE_STATUS)
        if (status == GL_FALSE) {

            val infoLog = glGetShaderInfoLog(shader)

            var type = ""
            when (shaderType) {
                GL_VERTEX_SHADER -> type = "vertex"
                GL_GEOMETRY_SHADER -> type = "geometry"
                GL_FRAGMENT_SHADER -> type = "fragment"
            }
            System.err.println("Compiler failure in $type shader: $infoLog")
        }

        return shader
    }

    fun createProgram(gl: GL3, shaderList: List<Int>): Int = with(gl) {

        val program = glCreateProgram()

        shaderList.forEach { glAttachShader(program, it) }

        glLinkProgram(program)

        val status = glGetProgram(program, GL_LINK_STATUS)
        if (status == GL_FALSE) {

            val strInfoLog = glGetProgramInfoLog(program)

            System.err.println("Linker failure: $strInfoLog")
        }

        shaderList.forEach { glDetachShader(program, it) }

        return program
    }

    fun initializeVertexBuffer(gl: GL3) = gl.initArrayBuffer(positionBufferObject) { data(vertexPositions, GL_STATIC_DRAW) }

    /**
     * Called to update the display. You don't need to swap the buffers after all of your rendering to display what you rendered,
     * it is done automatically.
     * @param gl
     */
    override fun display(gl: GL3) = with(gl) {

        clear {
            color()
            depth()
        }

        usingProgram(theProgram) {
            withVertexLayout(positionBufferObject, glf.pos4) { glDrawArrays(3) }
        }
    }

    /**
     * Called whenever the window is resized. The new window size is given, in pixels. This is an opportunity to call glViewport or
     * glScissor to keep up with the change in size.
     * @param gl
     * @param w
     * @param h
     */
    override fun reshape(gl: GL3, w: Int, h: Int) = with(gl) {
        glViewport(w, h)
    }

    /**
     * Called at the end, here you want to clean all the resources.
     * @param gl
     */
    override fun end(gl: GL3) = with(gl) {

        glDeleteProgram(theProgram)
        glDeleteBuffer(positionBufferObject)
        glDeleteVertexArray(vao)

        destroyBuffers(vao, positionBufferObject, vertexPositions)
    }

    /**
     * Called whenever a key on the keyboard was pressed. The key is given by the KeyCode(). It's often a good idea to have the escape
     * key to exit the program.
     * @param keyEvent
     */
    override fun keyPressed(keyEvent: KeyEvent) {

        when (keyEvent.keyCode) {
            KeyEvent.VK_ESCAPE -> quit()
        }
    }
}